/*
 *  Copyright (c) 2001 Sun Microsystems, Inc.  All rights
 *  reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *  1. Redistributions of source code must retain the above copyright
 *  notice, this list of conditions and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in
 *  the documentation and/or other materials provided with the
 *  distribution.
 *
 *  3. The end-user documentation included with the redistribution,
 *  if any, must include the following acknowledgment:
 *  "This product includes software developed by the
 *  Sun Microsystems, Inc. for Project JXTA."
 *  Alternately, this acknowledgment may appear in the software itself,
 *  if and wherever such third-party acknowledgments normally appear.
 *
 *  4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA"
 *  must not be used to endorse or promote products derived from this
 *  software without prior written permission. For written
 *  permission, please contact Project JXTA at http://www.jxta.org.
 *
 *  5. Products derived from this software may not be called "JXTA",
 *  nor may "JXTA" appear in their name, without prior written
 *  permission of Sun.
 *
 *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 *  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *  DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 *  ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 *  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 *  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 *  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 *  SUCH DAMAGE.
 *  ====================================================================
 *
 *  This software consists of voluntary contributions made by many
 *  individuals on behalf of Project JXTA.  For more
 *  information on Project JXTA, please see
 *  <http://www.jxta.org/>.
 *
 *  This license is based on the BSD license adopted by the Apache Foundation.
 *
 *  $Id: JxtaTreeModel.java,v 1.8 2006/05/17 23:23:05 nano Exp $
 */

package net.jxta.myjxta.ui.model;

import net.jxta.myjxta.util.Constants;
import net.jxta.myjxta.util.Group;
import net.jxta.myjxta.util.Resources;
import net.jxta.myjxta.util.objectmodel.GroupNode;
import net.jxta.myjxta.util.objectmodel.JxtaNode;
import net.jxta.myjxta.util.objectmodel.PeerNode;
import net.jxta.myjxta.util.objectmodel.ShareNode;
import org.jdesktop.swingx.treetable.DefaultTreeTableModel;
import org.jdesktop.swingx.treetable.TreeTableModel;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ResourceBundle;


/**
 * The data model for the nodes displayed in the TreeTable
 *
 * @version $Id: JxtaTreeModel.java,v 1.8 2006/05/17 23:23:05 nano Exp $
 *
 * @author james todd [gonzo at jxta dot org]
 */

public final class JxtaTreeModel
extends DefaultTreeTableModel {
    
    /** The list of columns we display */
    private static final String[] columnNames;
    
    /**
     *  The column types for ths columns defined in
     *  {@link #columnNames columnNames}.
     * DAW: should the first column type not be JxtaNode since that is
     * what is returned by  getValueAt
     */
    private static final Class[] columnTypes = {
        TreeTableModel.class, String.class
    };

    private List<JxtaTreeModelListener> listeners = null;
    
    static {
        ResourceBundle strings = Resources.getStrings();
        
        columnNames = new String[2];
        
        columnNames[0] = "Networks";
        columnNames[1] = "Description";
    }
    
    /**
     * Create a new JxtaModel, which has a root JXtaNode with
     * a label of "JXTA"
     */
    public JxtaTreeModel() {
        super(new JxtaNode(null, Constants.ROOT_NODE));
    }


    /**
     * We dont want to add a PeerNode to the tree, but we want to be sure that the group
     * the node lies in is visible.

     * @param node 
     */
    public void ensurePeerNodeHasVisibleParent(PeerNode node){
        Group g = ((GroupNode)node.getParent()).getGroup();

        getParentNode(g); //this call to getParentNode ensures that there is a parent node
                          //and creates it otherwise
    }
    /**
     * Add a new JxtaNode to be displayed in the tree
     *
     * @param node the node to add
     * @return true if the node was added, false if the node already
     *         existed in the tree
     */
    
    public boolean add(JxtaNode node) {
        boolean isAdded = false;
        
        if (node instanceof GroupNode) {
            Group g = ((GroupNode)node).getGroup();
            JxtaNode pn = getParentNode(g.getParentGroup());
            
            if (pn != null) {
                isAdded = addTreeNode(pn, node);
            }
        } else if (node instanceof PeerNode) {
            Group g = ((GroupNode)node.getParent()).getGroup();
            JxtaNode pn = getParentNode(g);
            int i = pn != null ? pn.getIndexOf(node) : -1;

            if (pn != null &&
                i == -1) {
                isAdded = addTreeNode(pn, node);
            }
        } else if (node instanceof ShareNode) {
            Group g = ((GroupNode)node.getParent()).getGroup();
            JxtaNode pn = getParentNode(g);
            int i = pn != null ? pn.getIndexOf(node) : -1;
            
            if (pn != null &&
                i == -1) {
                isAdded = addTreeNode(pn, node);
            }
        }
        
        if (isAdded) {
            for (Iterator l = getListeners(); l.hasNext(); ) {
                ((JxtaTreeModelListener)l.next()).nodeAdded(node);
            }
        }
        
        return isAdded;
    }
        
    /**
     * Remove the indicated node
     *
     * @param node the JxtaNode to remove
     */
    public void remove(JxtaNode node) {
        JxtaNode parent = (JxtaNode)node.getParent();
        
        if (parent != null) {
            int changedIndex[] = new int[1];
            JxtaNode changedNodes[] = new JxtaNode[1];
            changedNodes[0] = node;
            changedIndex[0] = parent.getIndexOf(node);
            
            parent.remove(node);
            
            fireTreeNodesRemoved(this, parent.getPath(), changedIndex,
                changedNodes);
            
            for (Iterator l = getListeners(); l.hasNext(); ) {
                ((JxtaTreeModelListener)l.next()).nodeRemoved(node);
            }
        }
    }
           
    /**
     * The indicated Tree node has changed
     */
    public void nodeChanged(JxtaNode node) {
        int changedIndex[] = new int[1];
        JxtaNode changedNodes[] = new JxtaNode[1];
        JxtaNode parent = (JxtaNode)node.getParent();
        
        if (parent != null) {
            changedNodes[0] = node;
            changedIndex[0] = parent.getIndexOf(node);
            
            fireTreeNodesChanged(this,parent.getPath(), changedIndex,
                changedNodes);
            
            for (Iterator l = getListeners(); l.hasNext(); ) {
                ((JxtaTreeModelListener)l.next()).nodeUpdated(node);
            }
        }
    }
    
    public void setValueAt(Object v, Object n, int c) {
    }
    
    /**
     * Get the number of children of the indicated node
     *
     * @param node the object for which to get the number of children
     */
    public int getChildCount(Object node) {
        return ((JxtaNode)node).getChildrenCount();
    }
    
    /**
     * Get the child of the indicated node at the indicated index
     *
     * @param node the  node from which to get the child
     * @param index the index of the desired child
     *
     * @return the child at the indicated index at the indicated node
     */
    public Object getChild(Object node, int index) {
        return node != null ? ((JxtaNode)node).getChildAt(index) : null;
    }
    
    /**
     * Check whether the indicated node is a leaf node
     *
     * @param node the node for which to check the status
     *
     * @return true if node is a lead node, false otherwise
     */
    public boolean isLeaf(Object node) {
        return ((JxtaNode)node).isLeaf();
    }
    
    /**
     * Get the number of columns to be displayed int
     * the TreeTable
     *
     * @return the desired number of columns
     */
    public int getColumnCount() {
        return columnNames.length;
    }
    
    /**
     * Get the name of the indicated column
     *
     * @param column the index of the column
     *
     * @return the name for the indicated column
     */
    public String getColumnName(int column) {
        return columnNames[column];
    }
    
    /**
     * Get the name of the indicated column
     *
     * @param column the index of the column
     *
     * @return the name for the indicated column
     */
    public Class getColumnClass(int column) {
        return columnTypes[column];
    }
    
    /**
     * Get the object that represents the indicated column for the
     * indicated node.
     *
     * @param node the node for which to get the object
     * @param column the column which we want to print
     *
     * @return an object that represents the indicated column for the
     *         indicated node
     */
    public Object getValueAt(Object node, int column) {
        Object o = null;
        
        switch (column) {
            case 0:
                o = (JxtaNode)node;
                
                break;
            case 1:
                o = ((JxtaNode)node).getDescription();
                
                break;
        }
        
        return o;
    }

    /**
     * Visit all nodes and change their inactive/active status
     */
    public void checkStatus() {
        JxtaNode n = (JxtaNode)getRoot();
        
        for (Iterator i = n.getChildren(); i.hasNext(); ) {
            checkNodeStatus((JxtaNode)i.next());
        }
    }

    public Iterator getListeners() {
        return this.listeners != null ?
            this.listeners.iterator() : Collections.EMPTY_LIST.iterator();
    }

    /**
     * Find the group node to which we want to add this Group instance
     *
     * @param group the group for which to locate the parent GroupNode
     * @return the parent GroupNode
     */
    
    protected JxtaNode getParentNode(Group group) {
        JxtaNode n = null;
        
        if (group != null &&
            group.isVisible()) {
            JxtaNode p = null;
            Group pg = group.getParentGroup();
            
            if (pg != null &&
                pg.isVisible()) {
                p = getParentNode(pg);
                n = new GroupNode(group);
            } else {
                p = (JxtaNode)getRoot();
                n = new GroupNode(group);
            }
            
            int i = p.getIndexOf(n);
            
            if (i > -1) {
                n = (GroupNode)p.getChildAt(i);
            } else {
                addTreeNode(p, n);
            }
        }
        
        return n;
    }
    
    /**
     * Add a child node to the indicated parent and notify the model
     * that the tree  structure changed
     *
     * @param parent the node to which  to add the child
     * @param child the child to add
     * @return true if the node was added to the tree, false
     *         if the node already existed in the tree
     */
    
    private boolean addTreeNode(JxtaNode parent, JxtaNode child) {
        boolean isAdded = false;
        int changedIndex[] = new int[1];
        JxtaNode changedNodes[] = new JxtaNode[1];
        boolean firstNode = false;
        
        if (parent == root &&
            parent.getChildrenCount() == 0) {
            firstNode = true;
        }
        
        if (parent.contains(child)) {
            // we reparent to make sure that JxtaNode parent structure
            // is in sync with JTree structure.
            // This is needed as getParentNode creates new instances.
            // Therefore it needs to be done before the check of
            // parent.contains as that depends on ID value not on instance
            child.setParent(parent);
            
            // inform the child node that it is being accessed
            // and inform the tree if this is a status change
            changedIndex[0] = parent.getIndexOf(child);
            changedNodes[0] = (JxtaNode)parent.getChildAt(changedIndex[0]);
            
            if (changedNodes[0].informAccessed()) {
                fireTreeNodesChanged(this, parent.getPath(), changedIndex,
                    changedNodes);
            }
            
            isAdded = false;
        } else {
            parent.add(child);
            child.setParent(parent);   // see comment in if block
        
            changedNodes[0] = child;
            changedIndex[0] = parent.getIndexOf(child);
        
            // the very first node changes the structure
            // if we just call fireTreeNodesInserted the tree
            // does not get updated
            if (firstNode) {
                fireTreeStructureChanged(this, parent.getPath(), changedIndex,
                changedNodes);
            } else {
                fireTreeNodesInserted(this, parent.getPath(), changedIndex,
                changedNodes);
            }
            
            isAdded = true;
        }
        
        return isAdded;
    }
    
    private void checkNodeStatus(JxtaNode n) {
        int changedIndex[] = new int[1];
        JxtaNode changedNodes[] = new JxtaNode[1];
        JxtaNode parent = (JxtaNode)n.getParent();
        
        if (n.checkStatus()) {
            changedNodes[0] = n;
            changedIndex[0] = parent.getIndexOf(n);
            
            fireTreeNodesChanged(this, parent.getPath(), changedIndex,
                changedNodes);
        }
        
        for (Iterator i = n.getChildren(); i.hasNext(); ) {
            checkNodeStatus((JxtaNode)i.next());
        }
    }
}
